package com.jdd.modules.system.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jdd.common.api.vo.Result;
import com.jdd.common.aspect.annotation.Dict;
import com.jdd.common.aspect.annotation.OperationLogDetail;
import com.jdd.common.constant.CommonConstant;
import com.jdd.common.system.vo.LoginUser;
import com.jdd.common.util.*;
import com.jdd.modules.operation.log.entity.OperationLog;
import com.jdd.modules.operation.log.service.IOperationLogService;
import com.jdd.modules.system.entity.SysUser;
import com.jdd.modules.system.service.ISysDictService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * @Description: 字典aop类
 * @Author: dangzhenghui
 * @Date: 2019-3-17 21:50
 * @Version: 1.0
 */
@Aspect
@Component
@Slf4j
public class DictAspect {
    @Autowired
    private ISysDictService dictService;

    @Autowired
    private IOperationLogService logService;

    /**
     * date类的包名
     */
    private static final String JAVA_DATE_CLASS_PACKAGE = "java.util.Date";

    /**
     * 定义切点Pointcut
     */
    @Pointcut("execution(public * com.jdd.modules..*.*Controller.*(..))")
    public void excudeService() {
    }

    /**
     * 自定义注解
     */
    @Pointcut("@annotation(com.jdd.common.aspect.annotation.OperationLogDetail)")
    public void controllerAspect() {

    }

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        log.info("接收到请求");
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 只记录post方法
        if("POST".equals(request.getMethod())){
            // 记录下请求内容
            log.info("请求URL : " + request.getRequestURL());
            log.info("客户端ip ["+ request.getRemoteAddr() +"]");
            log.info("请求映射 : 【" + pjp.getSignature().getDeclaringTypeName() + "】类的【" + pjp.getSignature().getName() + "】方法");

            // 获取参数, 只取自定义的参数, 自带的HttpServletRequest, HttpServletResponse不管
            if (pjp.getArgs().length > 0) {
                for (Object o : pjp.getArgs()) {
                    if (Objects.isNull(o) || o instanceof MultipartFile || o instanceof MultipartFile[] || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof HttpSession) {
                        continue;
                    }

                    log.info("请求参数 : " + JSON.toJSONString(o));
                }
            }
        }
        // 只记录post方法
        if("GET".equals(request.getMethod())){
            // 记录下请求内容
            log.info("请求URL : " + request.getRequestURL());
            log.info("请求IP : " + request.getRemoteAddr());
            log.info("请求映射 : 【" + pjp.getSignature().getDeclaringTypeName() + "】类的【" + pjp.getSignature().getName() + "】方法");

            // 获取参数, 只取自定义的参数, 自带的HttpServletRequest, HttpServletResponse不管
            if (pjp.getArgs().length > 0) {
                for (Object o : pjp.getArgs()) {
                    if (Objects.isNull(o) || o instanceof MultipartFile || o instanceof MultipartFile[] || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof HttpSession) {
                        continue;
                    }
                    log.info("请求参数 : " + JSON.toJSONString(o));
                }
            }
        }



        long time1 = System.currentTimeMillis();
        Object result = pjp.proceed();
        long time2 = System.currentTimeMillis();
        log.debug("获取JSON数据 耗时：" + (time2 - time1) + "ms");
        long start = System.currentTimeMillis();
        this.parseDictText(result);
        long end = System.currentTimeMillis();
        end = System.currentTimeMillis() - end;
        //addOperationLog(pjp,result,end);
        log.debug("解析注入JSON数据  耗时" + (end - start) + "ms");
        if (result instanceof ModelAndView) {
            return result;
        }
        log.info("返回：{}", JSONObject.toJSONString(result));
        return result;
    }

    /**
     * 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
     * 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 ，table字典 code table text配合使用与原来jeecg的用法相同
     * 示例为SysUser   字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text，已字段名称加_dictText形式返回到前端
     * 例输入当前返回值的就会多出一个sex_dictText字段
     * {
     * sex:1,
     * sex_dictText:"男"
     * }
     * 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
     * customRender:function (text) {
     * if(text==1){
     * return "男";
     * }else if(text==2){
     * return "女";
     * }else{
     * return text;
     * }
     * }
     * 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
     *
     * @param result
     */
    private void parseDictText(Object result) {
        if (result instanceof Result) {
            if (((Result) result).getResult() instanceof IPage) {
                List<JSONObject> items = new ArrayList<>();
                for (Object record : ((IPage) ((Result) result).getResult()).getRecords()) {
                    ObjectMapper mapper = new ObjectMapper();
                    String json = "{}";
                    try {
                        //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
                        json = mapper.writeValueAsString(record);
                    } catch (JsonProcessingException e) {
                        log.error("json解析失败" + e.getMessage(), e);
                    }
                    JSONObject item = JSONObject.parseObject(json);
                    //update-begin--Author:scott -- Date:20190603 ----for：解决继承实体字段无法翻译问题------
                    //for (Field field : record.getClass().getDeclaredFields()) {
                    for (Field field : oConvertUtils.getAllFields(record)) {
                        //update-end--Author:scott  -- Date:20190603 ----for：解决继承实体字段无法翻译问题------
                        if (field.getAnnotation(Dict.class) != null) {
                            String code = field.getAnnotation(Dict.class).dicCode();
                            String text = field.getAnnotation(Dict.class).dicText();
                            String table = field.getAnnotation(Dict.class).dictTable();
                            String key = String.valueOf(item.get(field.getName()));

                            //翻译字典值对应的txt
                            String textValue = translateDictValue(code, text, table, key);

                            log.debug(" 字典Val : " + textValue);
                            log.debug(" __翻译字典字段__ " + field.getName() + CommonConstant.DICT_TEXT_SUFFIX + "： " + textValue);
                            item.put(field.getName() + CommonConstant.DICT_TEXT_SUFFIX, textValue);
                        }
                        //date类型默认转换string格式化日期
                        if (JAVA_DATE_CLASS_PACKAGE.equals(field.getType().getName()) && field.getAnnotation(JsonFormat.class) == null && item.get(field.getName()) != null) {
                            SimpleDateFormat aDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
                        }
                    }
                    items.add(item);
                }
                ((IPage) ((Result) result).getResult()).setRecords(items);
            }

        }
    }

    /**
     * 翻译字典文本
     *
     * @param code
     * @param text
     * @param table
     * @param key
     * @return
     */
    private String translateDictValue(String code, String text, String table, String key) {
        if (oConvertUtils.isEmpty(key)) {
            return null;
        }
        StringBuffer textValue = new StringBuffer();
        String[] keys = key.split(",");
        for (String k : keys) {
            String tmpValue = null;
            log.debug(" 字典 key : " + k);
            if (k.trim().length() == 0) {
                continue; //跳过循环
            }
            if (!StringUtils.isEmpty(table)) {
                tmpValue = dictService.queryTableDictTextByKey(table, text, code, k.trim());
                log.info("table:{}",table);
            } else {
                tmpValue = dictService.queryDictTextByKey(code, k.trim());
                log.info("temValue",tmpValue);
            }

            if (tmpValue != null) {
                if (!"".equals(textValue.toString())) {
                    textValue.append(",");
                }
                textValue.append(tmpValue);
            }

        }
        return textValue.toString();
    }

    /**
     * 环绕通知
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("controllerAspect()")
    public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {
        log.info("环绕通知开始>>>>>>>>>>>>>>");
        long startTime;
        long runTime = 0;
        Object result ;
        //异常描述
        String exceptionMsg = null;
        //异常代码
        String exceptionCode = null;
        try {
            // 获取开始时间
            startTime = System.currentTimeMillis();
            // 获取方法运行时间
            runTime = System.currentTimeMillis() - startTime;
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            //异常描述
            exceptionMsg = throwable.getMessage();
            //异常类型
            exceptionCode = throwable.getClass().getName();
            throwable.printStackTrace();
            result = Result.error(exceptionMsg);
        }
        try {
            addPerformLog(proceedingJoinPoint, result, runTime, exceptionMsg, exceptionCode);
            log.error("异常日志:" + exceptionMsg);
            log.info("环绕通知结束>>>>>>>>>>>>>>");
        } catch (Throwable throwable) {
            log.info("操作日志新增失败:" + throwable.getMessage());
            throwable.printStackTrace();
        }
        log.info("环绕通知结束>>>>>>>>>>>>>>");
        return result;
    }

    /**
     * 操作日志保存
     *
     * @param joinPoint     切面方法的信息
     * @param ret           返回值
     * @param runTime       方法运行时间
     * @param exceptionMsg  异常描述
     * @param exceptionCode 异常代码
     */
    private void addPerformLog(JoinPoint joinPoint, Object ret, long runTime, String exceptionMsg, String exceptionCode) {
        //获取当前登录用户信息
        LoginUser sysUser = LoginUserUtils.getLoginUser();
        //登录人名称
        String username = null;
        String parkCode = null;
        String parkName = null;
        if (null != sysUser) {
            username = sysUser.getUsername();
            parkCode = sysUser.getParkCode();
            parkName = sysUser.getParkName();
        } else {
            Result result = (Result) ret;
            if (null != result) {
                Map<String, SysUser> resultMap = (Map<String, SysUser>) result.getResult();
                if(resultMap != null){
                	SysUser user = resultMap.get("userInfo");
                	if (null != user) {
                		username = user.getUsername();
                	}
                }
            }
        }

        //过滤后序列化无异常
        Object[] args = joinPoint.getArgs();
        Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
        List<Object> logArgs = stream
                .filter(arg -> (
                		!(arg instanceof BeanPropertyBindingResult) &&
                		!(arg instanceof HttpServletRequest) &&
                		!(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        //返回值
        String params = JSON.toJSONString(logArgs);
        //注解参数值
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            map = getServiceMethodDescription(joinPoint);

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        // 方法描述
        String describe = (String) map.get("describe");
        // 日志等级
        int level = (int) map.get("level");
        // 操作类型
        String operationType = (String) map.get("operationType");
        // 被操作的对象
        String operationUnit = (String) map.get("operationUnit");
        //方法
        String method = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //返回值
        String returnValue = JSON.toJSONString(ret);

        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();

        OperationLog operationLog = new OperationLog();
        operationLog.setId(SnowFlakeUtil.getId());
        operationLog.setLevel(level);
        operationLog.setOperationUnit(operationUnit);
        operationLog.setMethod(method);
        operationLog.setArgs(params);
        operationLog.setUsername(username);
        operationLog.setDescribe(describe);
        operationLog.setOperationType(operationType);
        operationLog.setRunTime((int) runTime);
        operationLog.setReturnValue(returnValue);
        operationLog.setCreateTime(new Date());
        operationLog.setExceptionMsg(exceptionMsg);
        operationLog.setExceptionCode(exceptionCode);
        operationLog.setParkCode(parkCode);
        operationLog.setParkName(parkName);
        operationLog.setIp(IPUtils.getIpAddr(request));

        log.info("记录异常日志:" + operationLog);
        logService.save(operationLog);
    }

    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切面方法的信息
     * @return
     * @throws ClassNotFoundException
     */
    public static Map<String, Object> getServiceMethodDescription(JoinPoint joinPoint) throws Throwable {
        //注解参数集合
        Map<String, Object> defaultMap = new HashMap<String, Object>();

        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    OperationLogDetail annotation = method.getAnnotation(OperationLogDetail.class);
                    if (null != annotation) {
                        defaultMap.put("level", annotation.level().getValue());
                        defaultMap.put("describe", annotation.detail());
                        defaultMap.put("operationType", annotation.operationType().getValue());
                        defaultMap.put("operationUnit", annotation.operationUnit().getValue());
                        break;
                    }
                }
            }
        }
        return defaultMap;
    }


}
